package net.miranti.dao;

import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.persistence.EntityManager;
import javax.persistence.EntityNotFoundException;
import javax.persistence.Query;

import com.google.inject.Inject;

public class JpaDao implements GenericDao {
	private final EntityManager em;
	
	// used by Spring
	JpaDao() {
		em = null;
	}

	// used by Guice
	@Inject
	public JpaDao(EntityManager em) {
		this.em = em;
	}
	
	private EntityManager getEntityManager() {
		return em!=null ? em : ResourceLocalDaoUtils.getEntityManager();
	}
	
	// PERSIST / UPDATE = MERGE (cobre ambos)
	public<T> T save(T entity) {
		return getEntityManager().merge(entity);
	}
	// DELETE
	public void delete(Object entity) {
		getEntityManager().remove(entity);
	}
	
	public void updateQuery(String query, Object... params) {
		Query q = getEntityManager().createQuery(query);
		bindIndexParameters(q, params);
		q.executeUpdate();
	}
	public void updateQuery(String query, Map<String, Object> params) {
		Query q = getEntityManager().createQuery(query);
		bindNamedParameters(q, params);
		q.executeUpdate();
	}
	public void namedUpdateQuery(String query, List params) {
		namedUpdateQuery(query, params.toArray());
	}
	public void namedUpdateQuery(String query, Object... params) {
		Query q = getEntityManager().createNamedQuery(query);
		bindIndexParameters(q, params);
		q.executeUpdate();
	}
	public void namedUpdateQuery(String query, Map<String, Object> params) {
		Query q = getEntityManager().createNamedQuery(query);
		bindNamedParameters(q, params);
		q.executeUpdate();
	}

	// READ
	public<T> T findById(Class<T> c, Object id) {
		T res = getEntityManager().find(c, id);
		if (res==null)
			throw new EntityNotFoundException(c.getName() + "/id: " + id);
		return res;
	}
	public<T> List<T> findAll(Class<T> c) {
		return getEntityManager().createQuery("from " + c.getSimpleName()).getResultList();
	}

	public List query(String query, Integer firstResult, Integer maxResults, Object... params) {
		return getIndexSearchResultList(
			getEntityManager().createQuery(query), 
			firstResult, maxResults, params);
	}
	public List query(String query, List params) {
		return query(query, params.toArray());
	}
	public List query(String query, Object... params) {
		return query(query, null, null, params);
	}
	public List query(String query, Map<String, Object> params) {
		return query(query, null, null, params);
	}
	public List query(String query, Integer firstResult, Integer maxResults, Map<String, Object> params) {
		return getNamedParametersSearchResultList(
			getEntityManager().createQuery(query),
			firstResult, maxResults, params);
	}

	public List namedQuery(String query, Integer firstResult, Integer maxResults, Object... params) {
		return getIndexSearchResultList(
			getEntityManager().createNamedQuery(query), 
			firstResult, maxResults, params);
	}
	public List namedQuery(String query, List params) {
		return query(query, params.toArray());
	}
	public List namedQuery(String query, Object... params) {
		return namedQuery(query, null, null, params);
	}
	
	public List namedQuery(String query, Integer firstResult, Integer maxResults, Map<String, Object> params) {
		return getNamedParametersSearchResultList(
			getEntityManager().createNamedQuery(query),
			firstResult, maxResults, params);
	}
	public List namedQuery(String query, Map<String, Object> params) {
		return namedQuery(query, null, null, params);
	}

	private List getIndexSearchResultList(Query q, Integer f, Integer m, Object... params) {
		bindIndexParameters(q, params);
		setPagination(q, f, m);
		return q.getResultList();
	}
	private List getNamedParametersSearchResultList(Query q, Integer f, Integer m, Map<String, Object> params) {
		bindNamedParameters(q, params);
		setPagination(q, f, m);
		return q.getResultList();
	}
	
	private void bindIndexParameters(Query q, Object... params) {
		for (int i=0; i<params.length; i++)
			q.setParameter(i+1, params[i]);
	}
	private void bindNamedParameters(Query q, Map<String, Object> params) {
		for (String p : params.keySet())
			q.setParameter(p, params.get(p));
	}
	private void setPagination(Query q, Integer firstResult, Integer maxResults) {
		if (firstResult!=null)
			q.setFirstResult(firstResult);
		if (maxResults!=null)
			q.setMaxResults(maxResults);
	}

	public List dynaConstraintsQuery(Class clazz, Map constraints) {
		return dynaQuery(clazz, constraints, null, null);
	}
	public List dynaInitialsQuery(Class clazz, Map initials) {
		return dynaQuery(clazz, null, initials, null);
	}
	
	public List dynaKeywordsQuery(Class clazz, Map keywords) {
		return dynaQuery(clazz, null, null, keywords);
	}
	
	public List dynaQuery(Class clazz, Map constraints, Map keywords) {
		return dynaQuery(clazz, constraints, null, keywords);
	}
	
	public List dynaQuery(Class clazz, Map constraints, Map initials, Map keywords) {
		String className = clazz.getSimpleName(); 
		String rootPath = String.valueOf(className.charAt(0)).toLowerCase();
		StringBuilder sb = new StringBuilder("from ").append(className)
					.append(" ").append(rootPath).append(" where 1=1 ");
		List params = new LinkedList();
		if (constraints!=null)
			buildExpression(sb, rootPath, constraints, params, false, false);
		if (initials!=null)
			buildExpression(sb, rootPath, initials, params, false, true);
		if (keywords!=null)
			buildExpression(sb, rootPath, keywords, params, true, true);
		
		Query q = getEntityManager().createQuery(sb.toString());
		for (int i=0; i<params.size(); i++)
			q.setParameter(i+1, params.get(i));
		return q.getResultList();
	}
	
	private void buildExpression(StringBuilder sb, String root, Map map, 
			List params, Boolean initialWildcard, Boolean finalWildcard) {
		Set keys = map.keySet();
		Boolean isLikeStatement = initialWildcard || finalWildcard;
		for (Object k : keys) {
			Object restriction = map.get(k);
			if (restriction!=null) {
				sb.append(" and ").append(root).append(".").append(k)
				.append(isLikeStatement ? " like " : "=").append("? ");
				if (initialWildcard)
					restriction = "%" + restriction;
				if (finalWildcard)
					restriction = restriction + "%";
				params.add(restriction);
			}
		}
	}
}